package com.whately.core.base.utils;

import com.whately.core.base.exception.ServiceException;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;

public class AESUtils {
    // logback
    private final static Logger log = LoggerFactory.getLogger(AESUtils.class);

    /**
     * 注意key和加密用到的字符串是不一样的 加密还要指定填充的加密模式和填充模式 AES密钥可以是128或者256，加密模式包括ECB, CBC等
     * ECB模式是分组的模式，CBC是分块加密后，每块与前一块的加密结果异或后再加密 第一块加密的明文是与IV变量进行异或
     */
    public static final String KEY_ALGORITHM = "AES";
    public static final String ECB_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    public static final String CBC_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    public static final String PLAIN_TEXT = "MANUTD is the greatest club in the world";

    /**
     * IV(Initialization Value)是一个初始值，对于CBC模式来说，它必须是随机选取并且需要保密的
     * 而且它的长度和密码分组相同(比如：对于AES 128为128位，即长度为16的byte类型数组)
     *
     */
    public static final byte[] ivBytes = new byte[] { 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };

    /**
     * ECB模式进行加密,用key对data进行加密
     * @param key    AES密钥,默认UTF-8编码,必须为 128bit或256bit,即 16字节或32字节, 比如 "1234567890123456"
     * @param data   被加密的数据
     * @return  加密结果, 已经转换为Base64字符串
     */
    public static String encrypt(String key, String data)
             {
        try{
            byte[] dataBytes = data.getBytes();
            byte[] keyBytes = key.getBytes();

            //必须为 128bit或256bit,即 16字节或32字节
            if( keyBytes.length != 16 && keyBytes.length != 32 ){
                return "" ;
            }

            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            SecretKeySpec k =  new SecretKeySpec(keyBytes, KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, k);

            //因为Base64转码后可能包含有+,/,=这些不安全的URL字符串，所以要进行换字符
            // '+' -> '-'
            // '/' -> '_'
            // '=' -> '' 去掉=
            //String str = Base64.encodeBase64String(cipher.doFinal(dataBytes));
            String str = Base64.encodeBase64URLSafeString(cipher.doFinal(dataBytes));
           /* str=str.replaceAll("-","+");
            str=str.replaceAll("_","/");
            str=str.replaceAll("=","");*/
            return  str;
        }catch (Exception e){
            log.error("bad method aes encrypt",e);
            throw new ServiceException(400,"bad method aes encrypt");
        }
    }

    /**
     * ECB模式进行解密,用key对data进行解密
     * @param key   AES密钥, 默认UTF-8编码,必须为 128bit或256bit,即 16字节或32字节, 比如 "1234567890123456"
     * @param data  被解密的数据, (加密结果对Base64字符串)
     * @return  解密结果字符串
     */
    public static String decrypt(String key, String data)
            {
        try{
            //encodeBase64URLSafeString  需要转码data
            /*data=data.replaceAll("-","+");
            data=data.replaceAll("_","/");
            int mod = data.length()%4;
            if(mod>0){
                for(int i=0;i>4-mod;i++){
                    data = data+"=";
                }
            }*/

            byte[] keyBytes = key.getBytes();
            byte[] dataBytes = Base64.decodeBase64(data);

            //必须为 128bit或256bit,即 16字节或32字节
            if( keyBytes.length != 16 && keyBytes.length != 32 ){
                return "" ;
            }

            Cipher cipher = Cipher.getInstance("AES");
            SecretKeySpec k =  new SecretKeySpec(keyBytes, "AES");
            cipher.init(Cipher.DECRYPT_MODE, k);

            return  new String(cipher.doFinal(dataBytes));
        }catch (Exception e){
            log.error("bad method aes decrypt",e);
            throw new ServiceException(400,"bad method aes decrypt");
        }
    }


    /**
     * CBC模式进行加密,用key对data进行加密,并使用
     * @param keyBytes
     * @param dataBytes
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public static byte[] encryptCBC(byte[] keyBytes, byte[] dataBytes)
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance(CBC_CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(dataBytes);

    }

    /**
     *
     * @param keyBytes
     * @param dataBytes
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static byte[] decryptCBC(byte[] keyBytes, byte[] dataBytes)
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException, ClassNotFoundException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance(CBC_CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(dataBytes);

    }

    public static void main(String[] args) throws Exception {
        String salt = GUID16Utils.getGUID();
        String key = "1234567890123456";
        String keySalt= key+salt;
        String enStr=encrypt(keySalt,"10-1568971393");
        System.out.println(enStr);
        String deStr=decrypt(keySalt,enStr);
        System.out.println(deStr);

        //
        System.out.println(salt+enStr);
    }


}